/*    Copyright 2010 Tobias Marschall
 *
 *    This file is part of MoSDi.
 *
 *    MoSDi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    MoSDi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with MoSDi.  If not, see <http://www.gnu.org/licenses/>.
 */

package mosdi.matching;

import java.util.Arrays;

import mosdi.fa.Partition;
import mosdi.paa.MinimizableDAA;
import mosdi.util.ProductEncoder;

/** Similar to PatternMatchingDAA, but, here, the information about 
 *  the window position is encoded in the value rather than in the 
 *  state.
 */
public class PatternMatchingDAAAlternative extends MinimizableDAA {

	private AlgorithmCostScheme costScheme;
	private ProductEncoder stateEncoder;
	private ProductEncoder emissionEncoder;
	private ProductEncoder valueEncoder;
	private int maxTotalCost;
	
	public PatternMatchingDAAAlternative(AlgorithmCostScheme costScheme, int maxTotalCost) {
		this.costScheme = costScheme;
		this.maxTotalCost = maxTotalCost;
		int[] bases = new int[costScheme.windowSize()];
		Arrays.fill(bases, costScheme.alphabetSize());
		stateEncoder = new ProductEncoder(false,bases);
		emissionEncoder = new ProductEncoder(costScheme.maxWindowCost()+1, costScheme.windowSize());
		valueEncoder = new ProductEncoder(maxTotalCost+1, costScheme.windowSize());
	}

	public int decodeValueToCost(int value) {
		return valueEncoder.decodeComponent(0, value);
	}

	public int decodeEmissionToCost(int emission) {
		return emissionEncoder.decodeComponent(0, emission);
	}

	public int decodeEmissionToShift(int emission) {
		return emissionEncoder.decodeComponent(1, emission)+1;
	}
	
	public double[] costDistribution(double[] valueDistribution) {
		double[] costDistribution = new double[maxTotalCost+1];
		for (int value=0;value<getValueCount();++value) {
			costDistribution[decodeValueToCost(value)]+=valueDistribution[value];
		}
		return costDistribution;
	}

	@Override
	public int getAlphabetSize() {
		return costScheme.alphabetSize();
	}

	/** Decodes a state into the considered search window. */
	public int[] decodeState(int state) {
		return stateEncoder.decode(state);
	}
	
	@Override
	public int getEmission(int state) {
		int[] window = decodeState(state);
		return emissionEncoder.encode(costScheme.computeCost(window), costScheme.shift(window)-1);
	}

	@Override
	public int getEmissionCount() {
		return emissionEncoder.getValueCount();
	}

	@Override
	public int getStartState() {
		return 0;
	}

	@Override
	public int getStartValue() {
		return valueEncoder.encode(0,costScheme.windowSize()-1);
	}

	@Override
	public int getStateCount() {
		return stateEncoder.getValueCount();
	}

	@Override
	public int getTransitionTarget(int state, int character) {
		// System.out.println(String.format("transition from %s char %d to %s", Arrays.toString(stateEncoder.decode(state)), character, Arrays.toString(stateEncoder.decode(targetState))));
		return (state*costScheme.alphabetSize() + character) % stateEncoder.getValueCount(); 
	}

	@Override
	public int getValueCount() {
		return valueEncoder.getValueCount();
	}

	@Override
	public int performOperation(int state, int value, int emission) {
		int[] valueComponents = valueEncoder.decode(value);
		if (valueComponents[1]==0) {
			int[] emissionComponents = emissionEncoder.decode(emission);
			valueComponents[0] = Math.min(maxTotalCost, valueComponents[0]+emissionComponents[0]);
			valueComponents[1] = emissionComponents[1];
		} else {
			valueComponents[1] -= 1;
		}
		return valueEncoder.encode(valueComponents);
	}

	@Override
	public Partition getStatePartition() {
		Integer[] allEmissions = new Integer[getStateCount()];
		for (int i=0; i<getStateCount(); ++i) {
			allEmissions[i] = getEmission(i);
		}
		return new Partition(Arrays.asList(allEmissions));
	}

}
